﻿// Copyright (c) 2012, Alexander Endris
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#region Usings

using System;
using System.IO;
using BinaryReader = NamelessOne.Imaging.IO.BinaryReader;

#endregion

namespace NamelessOne.Imaging.Bmp
{
  /// <summary>
  ///   An image in BMP format
  /// </summary>
  internal class BmpImage : Image
  {
    #region Public / Methods

    /// <summary>
    ///   Führt anwendungsspezifische Aufgaben durch, die mit der Freigabe, der Zurückgabe oder dem Zurücksetzen von nicht verwalteten Ressourcen zusammenhängen.
    /// </summary>
    /// <filterpriority>2</filterpriority>
    public override void Dispose()
    {
      reader.Close();
      reader.Dispose();
    }

    /// <summary>
    ///   Gets the color values of the specified pixel
    /// </summary>
    /// <param name = "x">X-Coordinate of the pixel</param>
    /// <param name = "y">Y-Coordinate of the pixel</param>
    /// <returns>Color value of the specified pixel</returns>
    public override Color16 GetPixel(uint x, uint y)
    {
      long offset = (height - y - 1)*rowSize + x*3;

      reader.Seek(startPixelData + offset, SeekOrigin.Begin);

      byte[] data = reader.ReadBytes(3);

      return new Color16(data[2], data[1], data[0], (byte) 1);
    }

    /// <summary>
    ///   Reads the next line of pixels and outputs it as a byte array.
    ///   The PixelFormat property has to be taken into account so the values in the byte array can be interpreted correcly
    /// </summary>
    /// <param name = "scanline">A byte array that contains the raw data of the pixels in the scanline. If there is no more scanline the byte array will have a length of zero</param>
    /// <returns>True if there was another scanline, false if there wasn't</returns>
    public override bool ReadNextScanline(out byte[] scanline)
    {
      if (currentScanline >= height)
      {
        scanline = new byte[0];
        return false;
      }

      long offset = (height - currentScanline++ - 1)*rowSize;

      reader.Seek(startPixelData + offset, SeekOrigin.Begin);
      scanline = reader.ReadBytes(width*3);
      Array.Reverse(scanline);

      return true;
    }

    /// <summary>
    ///   Resets the internal scanline counter and streams so ReadNextScanline starts at the first scanline in the image.
    /// </summary>
    public override void ResetScanline()
    {
      currentScanline = 0;
    }

    /// <summary>
    ///   Not yet implemented and supported
    /// </summary>
    /// <param name = "x"></param>
    /// <param name = "y"></param>
    /// <param name = "value"></param>
    public override void SetPixel(uint x, uint y, Color16 value)
    {
      throw new NotSupportedException();
    }

    #endregion

    #region Private / Attributes

    private int currentScanline;
    private BinaryReader reader;
    private int rowSize;
    private long startPixelData;

    #endregion

    #region Private / Constructors

    internal BmpImage(string fileName)
    {
      imageFormat = ImageFormat.Bmp;
      Initialize(fileName);
    }

    #endregion

    #region Private / Methods

    /// <summary>
    ///   Initializes the image
    /// </summary>
    private void Initialize(string fileName)
    {
      FileStream s = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
      reader = new BinaryReader(s);
      {
        //Signature
        reader.ReadUInt16();

        uint size = reader.ReadUInt32();

        //Padding
        reader.ReadUInt32();
        startPixelData = reader.ReadUInt32();
        ReadImageHeader(reader);

        rowSize = (int) ((Math.Round((width*3f)/4))*4f);
      }
      reader.Seek(startPixelData, SeekOrigin.Begin);
    }

    /// <summary>
    ///   Reads the OS/X Version of the header
    /// </summary>
    private void ReadCoreHeader(System.IO.BinaryReader reader)
    {
      width = reader.ReadUInt16();
      height = reader.ReadUInt16();

      if (reader.ReadUInt16() != 1)
        throw new NotSupportedException("A value that should be 1 by definition isn't.");

      ushort bitsPerPixel = reader.ReadUInt16();

      if (bitsPerPixel < 8)
        throw new NotSupportedException("Unsupprted bit count per pixel.");

      bytesPerPixel = bitsPerPixel/8;
    }

    /// <summary>
    ///   Reads the image header
    /// </summary>
    private void ReadImageHeader(System.IO.BinaryReader reader)
    {
      uint size = reader.ReadUInt32();

      if (size == 12)
        ReadCoreHeader(reader);
      else
        ReadInfoHeader(reader);
    }

    /// <summary>
    ///   Reads the windows version of the header
    /// </summary>
    private void ReadInfoHeader(System.IO.BinaryReader reader)
    {
      width = reader.ReadInt32();
      height = reader.ReadInt32();

      if (reader.ReadUInt16() != 1)
        throw new NotSupportedException("A value that should be 1 by definition isn't.");

      ushort bitsPerPixel = reader.ReadUInt16();

      if (bitsPerPixel < 8)
        throw new NotSupportedException("Unsupprted bit count per pixel.");

      bytesPerPixel = bitsPerPixel/8;

      uint compression = reader.ReadUInt32();

      if (compression != 0)
        throw new NotSupportedException("Unsupported compression. Only Uncompressed BMP are supported.");
      //Image Size in bytes when compressed
      reader.ReadUInt32();

      // DPI (so to speak. but in meters nor inch)
      reader.ReadUInt32();
      reader.ReadUInt32();

      // Palette
      uint numColors = reader.ReadUInt32();
      uint numSignificantColors = reader.ReadUInt32();

      if (numColors != 0)
        throw new NotSupportedException("Paletted Bmps are not supported.");

      pixelFormat = PixelFormat.Rgb24;
    }

    #endregion
  }
}